﻿
/*==============================================================================
Copyright (c) Microsoft Corporation. All rights reserved.

This source code is intended only for use with Microsoft
Development Tools and/or on-line documentation.  See these other
materials for detailed information regarding Microsoft code samples.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS."  YOU BEAR THE RISK OF USING IT.  
MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTIES OR CONDITIONS.  
TO THE EXTENT PERMITTED UNDER YOUR LOCAL LAWS, MICROSOFT EXCLUDES 
THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NON-INFRINGEMENT.
=============================================================================================*/
/**********************************************************************************************
Module:  BackToBackUserAgent.cs
Description: The BackToBackUser class is the public API of this assembly. 
The purpose of this class is to allow Application writer to back to back an outstanding incoming 
call with an outgoing call targetting a an application-specified destination URI. 
BackToBack User Agent are used for different purposes in SIP networks. Typical usages of a B2BUA i
include third party call control, anonymization of the B2B destination (Front Office, Back Office 
separation when managing relations with customers), or the ability for an Application to remain in 
the signaling path and react to signaling events taking place in the middle of a call.

This class only exposes two methods: one to establish the back to back call and the other one to
terminate it. When used to contact an Information Worker registered on multiple UC endpoints, the 
B2B UA handles the multiple early dialogs/forks during call establishment until the call is accepted 
or declined by one of the forks.

**********************************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Collaboration.AudioVideo;
using Microsoft.Rtc.Signaling;


namespace BackToBack
{
    /// <summary>
    /// A back to back user agent object is a signaling helper API used to back to back two calls (an incoming call and an outgoing call).
    /// </summary>
    /// <remarks>
    /// The back to back class consists of two planes: the signaling plane (or Session Initiation Protocol (SIP) plane) and the Offer /
    /// Answer plane (or Session Description Protocol (SDP)plane). The former coordinates all the signaling actions required in the process
    /// of back to backing calls. These actions include initiating the outbound call, mapping early dialogs resulting from forks of the 
    /// outbound call with corresponding ones created by the B2BUA on the incoming call, relaying provisional responses from one call to the 
    /// other. The latter acts a broker responsible for returning an Answer for any given Offer. Note that during call establishment, the B2BUA
    /// may return multiple provisional Answers for a given initial Offer.
    /// </remarks>
    public class BackToBackUserAgent : IBackToBackCall
    {
        #region Fields
        /// <summary>
        /// The object to acquire lock on, when updating the internal state of 
        /// this object. This is to maintain the integrity of state across multiple threads.
        /// </summary>
        private object _syncRoot = new object();

        /// <summary>
        /// Initialize the current back-to-back state to 'Idle'
        /// </summary>
        private BackToBackState _currentBackToBackState = BackToBackState.Idle;

        /// <summary>
        /// The dictionary maintains the mapping b/w  the forks of the
        /// outgoing and incoming calls.
        /// </summary>
        private Dictionary<DialogContext /*IncomingDialogContext*/, DialogContext /*OutgoingDialogContext*/> _dialogContextMapping;

        /// <summary>
        /// References to the calls that are back to backed.
        /// </summary>
        private Call _Call1;
        private Call _Call2;

        /// <summary>
        /// The backToback provider is a MediaProvider implementation. Unlike a typical implementation, it does not actually aggregate 
        /// a Media Stack. One instance is typically bound to both the ougoing and incoming calls that are being back to backed. 
        /// The BackToBackProvider acts as an Offer/Answer broker between Call 1 and Call 2.
        /// </summary>
        private BackToBackProvider _mediaProvider;

        #endregion

        #region Constructors
        /// <summary>
        /// Creates one instance of a BackToBackUserAgent object.
        /// </summary>
        /// <param name="call1"> call1 must be a AudioVideoCall or derivative from B2BCall </param>
        /// <param name="call2"> call2 must be a AudioVideoCAll or derivative from B2BCall </param>
        public BackToBackUserAgent(Call call1, Call call2)
        {
            if ((call1 is AudioVideoCall || call1 is B2BCall) && (call2 is AudioVideoCall || call2 is B2BCall))
            {

                //Initializing fields.
                this.Call1 = call1;
                this.Call2 = call2;

                this.DialogContextMapping = new Dictionary<DialogContext, DialogContext>();

                this.MediaProvider = new BackToBackProvider();

                //Register events on incoming and outgoing calls.
                RegisterCall1Events();
                RegisterCall2Events();
            }
            else
            {
                throw new ArgumentException("At least one of the Call supplied in parameter is not an AudioVideoCall or a B2BCall.");
            }
        }
        #endregion

        #region Properties
        /// <summary>
        /// DialogContextMapping is a dictionary used by the B2BUA to maintain the mapping between a dialog on Call1 and a corresponding one on Call2
        /// and vice versa.
        /// </summary>
        private Dictionary<DialogContext, DialogContext> DialogContextMapping
        {
            get { return _dialogContextMapping; }
            set { _dialogContextMapping = value; }
        }

        private Call Call1
        {
            get { return _Call1; }
            set { _Call1 = value; }
        }

        private Call Call2
        {
            get { return _Call2; }
            set { _Call2 = value; }
        }

        private BackToBackProvider MediaProvider
        {
            get { return _mediaProvider; }
            set { _mediaProvider = value; }
        }

        #endregion

        #region Interface IBackToBackCall implementation

        /// <summary>
        /// Raised when the state of the BackToBack user agent instance is being updated.
        /// </summary>
        public event EventHandler<BackToBackStateChangedEventArgs> StateChanged;

        /// <summary>
        /// Gets the state of the BackToBack user agent instance.
        /// </summary>
        public BackToBackState State
        {
            get { return _currentBackToBackState; }
            private set { _currentBackToBackState = value; }
        }

        /// <summary>
        /// Initiates the Back To Back call by targetting the destination SIP URI supplied in parameter.
        /// </summary>
        /// <param name="destinationUri"> sip or tel uri corresponding to the destination of the back to back </param>
        /// <remarks> If application supplies null as destination URI, the default destination URI of the Conversation 
        /// will be used. For example, if the Conversation is already in a Conference, the corresponding MCU URI will be used
        /// as destination. Likewise, if the Conversation is already engaged in a P2P call with a given Remote Participant, the
        /// Remote Participant URI is used as destination URI of the back to back call. </remarks>
        public IAsyncResult BeginParticipate(string destinationUri, AsyncCallback userCallback, object state)
        {

            if (this.Call1.State == CallState.Incoming
                && this.Call2.State == CallState.Idle) // Outgoing call-flow
            {
                return ParticipateCore(
                    new BackToBackCalls(this.Call1 /*caller*/, this.Call2 /*callee*/, destinationUri),
                    userCallback,
                    state);
            }
            else if (this.Call2.State == CallState.Incoming
                && this.Call1.State == CallState.Idle) // Incoming call-flow
            {
                return ParticipateCore(
                    new BackToBackCalls(this.Call2 /*caller*/, this.Call1 /*callee*/, destinationUri),
                    userCallback,
                    state);
            }
            else
            {
                throw new InvalidOperationException(String.Format("Calls engaged in back-to-back session don't meet the Incoming/Outgoing callflow pre-requisites: call1 state {0}, call2 state {1}", this.Call1.State, this.Call2.State));
            }
        }

        /// <summary>
        /// Method invoked when Participate operation completes.
        /// </summary>
        public void EndParticipate(IAsyncResult asyncResult)
        {
            AsyncResultNoResult result = asyncResult as AsyncResultNoResult;
            result.EndInvoke();
        }

        /// <summary>
        /// Gracefully terminates the BackToBack Call.
        /// </summary>
        public IAsyncResult BeginTerminate(AsyncCallback userCallback, object state)
        {
            AsyncResultNoResult b2bTerminateAsyncResult = new AsyncResultNoResult(userCallback, state);            

            this.Call1.BeginTerminate((asyncResult) =>
                {
                    this.Call1.EndTerminate(asyncResult);

                    b2bTerminateAsyncResult.Data = this.Call2;
                    this.Call2.BeginTerminate(this.Call_TerminateCompleted_Callback, b2bTerminateAsyncResult);
                }, 
                this.Call1);

            return b2bTerminateAsyncResult;
        }

        /// <summary>
        /// Invoked to complete the Terminate operation
        /// </summary>
        public void EndTerminate(IAsyncResult asyncResult)
        {
            AsyncResultNoResult result = asyncResult as AsyncResultNoResult;

            result.EndInvoke();
        }
        #endregion


        #region Private Methods
        private void RegisterCall1Events()
        {
            this.Call1.ProvisionalResponseReceived += new EventHandler<CallProvisionalResponseReceivedEventArgs>(Call1_ProvisionalResponseReceived);
            this.Call1.StateChanged += new EventHandler<CallStateChangedEventArgs>(Call1_StateChanged);
        }

        private void UnRegisterCall1Events()
        {
            this.Call1.ProvisionalResponseReceived -= new EventHandler<CallProvisionalResponseReceivedEventArgs>(Call1_ProvisionalResponseReceived);
            this.Call1.StateChanged -= new EventHandler<CallStateChangedEventArgs>(Call1_StateChanged);
        }

        private void RegisterCall2Events()
        {
            this.Call2.ProvisionalResponseReceived += new EventHandler<CallProvisionalResponseReceivedEventArgs>(Call2_ProvisionalResponseReceived);
            this.Call2.StateChanged += new EventHandler<CallStateChangedEventArgs>(Call2_StateChanged);
        }

        private void UnRegisterCall2Events()
        {
            this.Call2.ProvisionalResponseReceived -= new EventHandler<CallProvisionalResponseReceivedEventArgs>(Call2_ProvisionalResponseReceived);
            this.Call2.StateChanged -= new EventHandler<CallStateChangedEventArgs>(Call2_StateChanged);
        }


        private AsyncResultNoResult ParticipateCore(BackToBackCalls b2bCalls, AsyncCallback userCallback, object state)
        {
            AsyncResultNoResult b2bParticipateAsyncResult = new AsyncResultNoResult(userCallback, state);

            // Bind the BackToBackProvider  instance to both calls.
            b2bCalls.Call2.BindMediaProvider(this.MediaProvider);
            b2bCalls.Call1.BindMediaProvider(this.MediaProvider); // Internally calls MediaProvider.SetInitialIncomingOffer(SdpOffer)

            CallEstablishOptions establishOptions = new CallEstablishOptions();
            establishOptions.SupportsEarlyMedia = CapabilitySupport.Supported;
            establishOptions.SupportsReplaces = CapabilitySupport.UnSupported; // Doesn't accept INVITE w/ REPLACES header

            b2bParticipateAsyncResult.Data = b2bCalls;

            try
            {
                if (String.IsNullOrEmpty(b2bCalls.DestinationUri))
                {
                    //when the destination URI is null, application writer indicates that it wants to use the default destination URI used by 
                    //Conversation.
                    b2bCalls.Call2.BeginEstablish(establishOptions,
                                                  CalleeCall_EstablishCompleted_Callback,
                                                  b2bParticipateAsyncResult);

                }
                else
                {
                    b2bCalls.Call2.BeginEstablish(b2bCalls.DestinationUri,
                                                  establishOptions,
                                                  CalleeCall_EstablishCompleted_Callback,
                                                  b2bParticipateAsyncResult);
                }
            }
            catch (InvalidOperationException ioex)
            {
                // Complete the AsyncResult with exception
                b2bParticipateAsyncResult.SetAsCompleted(ioex, true /*completed-synchronously*/);

            }

            return b2bParticipateAsyncResult;
        }

        private void RelayProvisionalAndEarlyMediaResponse(CallProvisionalResponseReceivedEventArgs e, Call receiver)
        {
            if (receiver.State == CallState.Terminated)
            {
                // No-op
                return;
            }

            if (e.DialogContext == null)
            {
                // No-op
                return;
            }

            DialogContext correspondingCallDialogContext = FindCorrespondingCallDialogContext(e.DialogContext);
            if (correspondingCallDialogContext == null)
            {
                try
                {
                    // Create new dialog-context
                    correspondingCallDialogContext = receiver.GetNewEarlyDialog();
                }
                catch (InvalidOperationException ioex)
                {
                    // Don't continue
                    return;
                }

                AddDialogContextMapping(e.DialogContext, correspondingCallDialogContext);
            }

            CallProvisionalResponseOptions provResponseOptions = new CallProvisionalResponseOptions();
            provResponseOptions.DialogContext = correspondingCallDialogContext;
            provResponseOptions.ResponseText = e.ResponseData.ResponseText;

            if (e.IsEarlyMedia)
            {
                RelayEarlyMediaResponse(provResponseOptions, receiver);
            }
            else
            {
                RelayProvisionalResponse(e.ResponseData.ResponseCode, provResponseOptions, receiver);
            }
        }

        /// <summary>
        /// Relays Early Media response (183 w/ SDP)
        /// </summary>
        private void RelayEarlyMediaResponse(CallProvisionalResponseOptions provResponseOptions, Call receiver)
        {
            try
            {
                // Good for both 'reliable' or 'non-reliable' early-media responses.
                if (receiver is AudioVideoCall)
                {
                    ((AudioVideoCall)receiver).BeginEstablishEarlyMedia(provResponseOptions, AudioVideoCall_EstablishEarlyMediaCompleted_Callback, receiver);
                }

            }
            catch (InvalidOperationException ioex)
            {
                //log
            }
            catch (RealTimeException rtex)
            {
                //log
            }
        }

        /// <summary>
        /// Relays non-reliable 'Provisional' response
        /// </summary>
        private void RelayProvisionalResponse(
            int responseCode,
            CallProvisionalResponseOptions provResponseOptions,
            Call receiver)
        {
            try
            {                
                // non-reliable provisional-response
                if (receiver is AudioVideoCall)
                {
                    ((AudioVideoCall)receiver).SendProvisionalResponse(responseCode, provResponseOptions);
                }
                else if (receiver is B2BCall)
                {
                    ((B2BCall)receiver).SendProvisionalResponse(responseCode, provResponseOptions);
                }
            }
            catch (InvalidOperationException ioex)
            {
                //log
            }
            catch (RealTimeException rtex)
            {
                //log
            }
        }


        private void UpdateBackToBackState(CallStateChangedEventArgs e, Call receiver)
        {
            if (e.State == CallState.Establishing && receiver.State == CallState.Incoming)
            {
                UpdateStateAndRaiseEvent(BackToBackState.Connecting);
              
            }
            else if (e.State == CallState.Established && receiver.State == CallState.Established)
            {
                // Both back-to-back calls are 'Established', so change the back-to-back state to 'Connected'
                UpdateStateAndRaiseEvent(BackToBackState.Connected);
            }
            else if (e.State == CallState.Terminating)
            {
                if (receiver.State != CallState.Terminating && receiver.State != CallState.Terminated)
                {
                    UpdateStateAndRaiseEvent(BackToBackState.Terminating);
                    TerminateCall(receiver);
                }
            }
        }

        private void DeclineCall(Call call, DialogContext dialogContext, int responseCode, string responseText, DiagnosticHeader diagnosticHeader)
        {

            CallDeclineOptions declineOptions = new CallDeclineOptions();
            declineOptions.ResponseCode = responseCode;
            declineOptions.ResponseText = responseText;

            if (diagnosticHeader != null)
                declineOptions.Headers.Add(new SignalingHeader(diagnosticHeader.HeaderName, diagnosticHeader.GetValue()));

            try
            {
                call.Decline(declineOptions);
            }
            catch (InvalidOperationException ioex)
            {
            }
            catch (RealTimeException rtex)
            {
            }
        }


        private void AddDialogContextMapping(DialogContext incomingDialogContext, DialogContext outgoingDialogContext)
        {
            // B2BMediaProvider consumes the 'DialogContext.ApplicationContext' information
            outgoingDialogContext.ApplicationContext = incomingDialogContext;
            incomingDialogContext.ApplicationContext = outgoingDialogContext;

            lock (_syncRoot)
            {
                this.DialogContextMapping.Add(incomingDialogContext, outgoingDialogContext);
           }
        }

        /// <summary>
        /// Returns the corresponding dialog-context from the dictionary, if there is 
        /// already one exists, otherwise returns null.
        /// </summary>
        private DialogContext FindCorrespondingCallDialogContext(DialogContext dialogContext)
        {
            DialogContext foundDialogContext = null;
            lock (_syncRoot)
            {
                this.DialogContextMapping.TryGetValue(dialogContext, out foundDialogContext);
            }
            return foundDialogContext;
        }

        /// <summary>
        /// Delete the preserved mappings from the dictionary for rest of the 
        /// dialog-contexts, except the one asked to keep. This method should be
        /// called to clean-up extra resources (contexts) that are not needed 
        /// anymore, after receiving the final successful response from one of 
        /// the dialog contexts.
        /// </summary>
        private void CleanupUnnecessaryDialogContexts(DialogContext dialogContextToKeep)
        {
            lock (_syncRoot)
            {
                DialogContext[] arrayOfDialogContexts = new DialogContext[this.DialogContextMapping.Count];
                this.DialogContextMapping.Keys.CopyTo(arrayOfDialogContexts, 0);

                foreach (DialogContext item in arrayOfDialogContexts)
                {
                    if (!item.Equals(dialogContextToKeep))
                    {
                        this.DialogContextMapping.Remove(item);
                    }
                }
            }
        }

        /// <summary>
        /// Terminates the call
        /// </summary>
        private void TerminateCall(Call call)
        {
            if (call.State != CallState.Terminating && call.State != CallState.Terminated)
            {
                AsyncResultNoResult asyncResult = new AsyncResultNoResult(null, null);
                asyncResult.Data = call;

                call.BeginTerminate(Call_TerminateCompleted_Callback, asyncResult);
            }
        }

        /// <summary>
        /// Update the back-to-back session state if it is allowed from the current state.
        /// Returns 'true' if state has been updated successfully.
        /// </summary>
        private bool TryUpdateState(BackToBackState newState, out BackToBackState oldState)
        {
            bool shouldUpdateState = false;

            lock (_syncRoot)
            {
                oldState = this.State;
                switch (oldState)
                {
                    case BackToBackState.Idle:
                        {
                            if (newState == BackToBackState.Connecting
                                || newState == BackToBackState.Terminating
                                || newState == BackToBackState.Terminated)
                            {
                                shouldUpdateState = true;
                            }
                            break;
                        }
                    case BackToBackState.Connecting:
                    {
                        if (newState == BackToBackState.Connected
                            || newState == BackToBackState.Terminating
                            || newState == BackToBackState.Terminated)
                        {

                            shouldUpdateState = true;
                        }

                        break;
                    }
                    
                    case BackToBackState.Connected:
                        {
                            if (newState == BackToBackState.Terminating
                                || newState == BackToBackState.Terminated)
                            {
                                shouldUpdateState = true;
                            }
                            break;
                        }

                    case BackToBackState.Terminating:
                        {
                            if (newState == BackToBackState.Terminated)
                            {
                                shouldUpdateState = true;
                            }
                            break;
                        }
                    case BackToBackState.Terminated:
                        {
                            if (newState == BackToBackState.Terminated)
                            {
                                shouldUpdateState = false;
                            }
                            break;
                        }
                } // end-of-switch

                if (shouldUpdateState)
                {
                    this.State = newState;
                }
            } // end-of-lock
            return shouldUpdateState;
        }

        private bool UpdateStateAndRaiseEvent(BackToBackState newState)
        {
            BackToBackState oldState;

            // Update state, if possible
            bool isUpdated = TryUpdateState(newState, out oldState);

            if (isUpdated)
            {
                // Fire event
                if (this.StateChanged != null)
                {
                    this.StateChanged(this, new BackToBackStateChangedEventArgs(oldState, newState));
                }
            }


            return isUpdated;
        }
        #endregion

        #region Event Handlers
        private void Call1_ProvisionalResponseReceived(object sender, CallProvisionalResponseReceivedEventArgs e)
        {
            RelayProvisionalAndEarlyMediaResponse(e, this.Call2 /*receiver*/);
        }

        private void Call1_StateChanged(object sender, CallStateChangedEventArgs e)
        {

            if (e.State == CallState.Terminated)
                UnRegisterCall1Events();

            UpdateBackToBackState(e, this.Call2 /*receiver*/);
        }

        private void Call2_ProvisionalResponseReceived(object sender, CallProvisionalResponseReceivedEventArgs e)
        {

            RelayProvisionalAndEarlyMediaResponse(e, this.Call1 /*receiver*/);
        }

        private void Call2_StateChanged(object sender, CallStateChangedEventArgs e)
        {
            if (e.State == CallState.Terminated)
                UnRegisterCall2Events();

            UpdateBackToBackState(e, this.Call1 /*receiver*/);
        }
        #endregion

        #region Callbacks
        private void Call_TerminateCompleted_Callback(IAsyncResult asyncResult)
        {
            AsyncResultNoResult b2bTerminateAsyncResult = asyncResult.AsyncState as AsyncResultNoResult;

            Call call = b2bTerminateAsyncResult.Data as Call;

            call.EndTerminate(asyncResult);

            UpdateStateAndRaiseEvent(BackToBackState.Terminated);
            // this was the last operation and everything completed successfully
            b2bTerminateAsyncResult.SetAsCompleted(null, false);
            
        }


        private void CallerCall_AcceptCompleted_Callback(IAsyncResult asyncResult)
        {

            AsyncResultNoResult b2bParticipateAsyncResult = asyncResult.AsyncState as AsyncResultNoResult;

            BackToBackCalls b2bCalls = b2bParticipateAsyncResult.Data as BackToBackCalls;

            Call call = b2bCalls.Call1;

            try
            {
                CallMessageData callMessageData = call.EndAccept(asyncResult);
                b2bParticipateAsyncResult.SetAsCompleted(null, false);
            }
            catch (RealTimeException rtex)
            {
                b2bParticipateAsyncResult.SetAsCompleted(rtex, false);
            }
        }

        private void AudioVideoCall_EstablishEarlyMediaCompleted_Callback(IAsyncResult asyncResult)
        {
            AudioVideoCall audioVideoCall = (AudioVideoCall)asyncResult.AsyncState;

            try
            {
                audioVideoCall.EndEstablishEarlyMedia(asyncResult);
            }
            catch (RealTimeException rtex)
            {
            }
        }

        private void CalleeCall_EstablishCompleted_Callback(IAsyncResult asyncResult)
        {
            AsyncResultNoResult b2bParticipateAsyncResult = asyncResult.AsyncState as AsyncResultNoResult;
            BackToBackCalls b2bCalls = b2bParticipateAsyncResult.Data as BackToBackCalls;

            CallMessageData callMessageData = null;
            try
            {
                callMessageData = b2bCalls.Call2.EndEstablish(asyncResult);
            }
            catch (FailureResponseException frex)
            {

                this.DeclineCall(b2bCalls.Call1,
                                 null,
                                 frex.ResponseData.ResponseCode,
                                 frex.ResponseData.ResponseText,
                                 frex.DiagnosticInformation);


                if (!b2bParticipateAsyncResult.IsCompleted)
                {
                    b2bParticipateAsyncResult.SetAsCompleted(frex, false); 
                }

                return;
            }
            catch (RealTimeException rtex)
            {
                int responseCodeToDeclineWith = 480; // TemporaryUnAvailable: default-decline-response-code

                this.DeclineCall(b2bCalls.Call1,
                                 null,
                                 responseCodeToDeclineWith,
                                 null,
                                 null);

                if (!b2bParticipateAsyncResult.IsCompleted)
                {               
                    b2bParticipateAsyncResult.SetAsCompleted(rtex, false);
                }
                return;
            }

            // CalleeCall.BeginEstablish() succeeded, so invoke Caller.BeginAccept()

            // Got successful answer from one; clean-up the other ones.
            CleanupUnnecessaryDialogContexts(callMessageData.DialogContext);


            // Corresponding CallDialogContext can be NULL, if answer is received 
            // from a branch that never sent provisonal-response before.
            DialogContext correspondingCallDialogContext = FindCorrespondingCallDialogContext(callMessageData.DialogContext);

            if (correspondingCallDialogContext == null)
            {
                try
                {
                    // Create new dialog-context for the corresponding call (Caller)
                    correspondingCallDialogContext = b2bCalls.Call1.GetNewEarlyDialog();
                }
                catch (InvalidOperationException ioex)
                {
                    // Don't continue
                    b2bParticipateAsyncResult.SetAsCompleted(ioex, false);
                    return;
                }

                AddDialogContextMapping(callMessageData.DialogContext, correspondingCallDialogContext);
            }

            try
            {
                CallAcceptOptions acceptOptions = new CallAcceptOptions();
                acceptOptions.SupportsReplaces = CapabilitySupport.UnSupported;
                acceptOptions.DialogContext = correspondingCallDialogContext;

                b2bCalls.Call1.BeginAccept(acceptOptions,
                                            CallerCall_AcceptCompleted_Callback,
                                            b2bParticipateAsyncResult);
            }
            catch (Exception ex)
            {
                b2bParticipateAsyncResult.SetAsCompleted(ex, false);
            }
        }
        #endregion


        #region internal class AsyncOperationHelper
        internal static class AsyncOperationHelper
        {

            internal static void GenericAsyncCallback(IAsyncResult asyncResult)
            {

                GenericEndAsyncState innerState =  asyncResult.AsyncState as GenericEndAsyncState;

                AsyncResultNoResult innerResult = innerState.UserAsyncResult;

                AsyncCallback endCallDelegate = innerState.EndCallDelegate;

                Exception innerException = null;

                try
                {

                    endCallDelegate(asyncResult);

                }

                catch (Exception ex)
                {

                    innerException = ex;

                }

                if (innerResult != null)
                {

                    innerResult.SetAsCompleted(innerException, true);

                }

            }

        }
        #endregion
        
        #region private class BackToBackCalls (Inner class)
        private class BackToBackCalls
        {
            private Call _call1;
            private Call _call2;
            private string _destinationUri;


            internal BackToBackCalls(Call caller, Call callee, string destinationUri)
            {
                _call1 = caller;
                _call2 = callee;
                _destinationUri = destinationUri;

            }

            internal Call Call1 { get { return _call1; } }
            internal Call Call2 { get { return _call2; } }
            internal string DestinationUri { get { return _destinationUri; } }

        }
        #endregion
    }
}